﻿using gt.rediscache.core.Connections.ClientServer;
using gt.rediscache.core.Entry;
using gt.rediscache.logger;
using StackExchange.Redis;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace gt.rediscache.core.Clients
{
    /// <summary>
    /// 支持主备节点 的主-备的切换模式
    /// </summary>
    public class SmartRedisClient : RedisClient
    {
        private RedisClient _backupClient;
        private SmartRedisClientConfiguration _configuration;
        private RedisClientServerPoolFactory _clientServerPoolFactory;

        public event EventHandler<ClientStateChangeEventArgs> OnClientStateChangedEvent;

        public SmartRedisClient(SmartRedisClientConfiguration options,
            RedisClientServerPoolFactory poolFactory)
            : base(options, poolFactory)
        {
            _configuration = options;
            if (options.BackupOptions != null && !CheckOptions(options.BackupOptions))
                throw new InvalidOperationException("RedisClientOptions property value error!");

            _clientServerPoolFactory = poolFactory;

            if (_configuration.SwitchNodes != null && _configuration.SwitchNodes.Any())
            {
                RedisLogManager.Info($"redisclient:{options.ClientName}  option switch nodes:{string.Join(",", _configuration.SwitchNodes)}");
                foreach (var nodeName in _configuration.SwitchNodes)
                {
                    var clientServer = GetClientServerByNodeName(nodeName);
                    if (clientServer != null)
                        ClientServerPool.FailedClientServer(clientServer);
                }
            }
            if (_configuration.AllowSync) ActiveBackupClient();
        }

        /// <summary>
        ///  do something when clientserver changed event
        /// </summary>
        protected override void HandleOnClientServerChanged(RedisClientServer clientServer, bool health)
        {
            base.HandleOnClientServerChanged(clientServer, health);
            if (!health) ActiveBackupClient();
            OnClientStateChangedEvent?.Invoke(this, new ClientStateChangeEventArgs { ClientName = _configuration.ClientName, Type = health ? 2 : 1, ClientServer = clientServer });
        }
        /// <summary>
        /// 执行Redis命令
        /// </summary>
        /// <param name="command">redis命令</param>
        /// <param name="message">message</param>
        /// <param name="commandFlags">commandFlags</param>
        /// <returns></returns>
        protected override EMRedisResult Execute(RedisCommand command, RedisMessage message, CommandFlags flags = CommandFlags.None)
        {
            var tempTuple = GetSmartClientServer(message.Key);
            var clientServer = tempTuple.Item1;
            var switched = tempTuple.Item2;
            var result = clientServer.ExecuteCommand(command, message, flags);

            var syncRedisClient = switched ? this : _backupClient;
            TrySyncMessage(syncRedisClient, command, message);

            return result;
        }
        /// <summary>
        /// 异步执行Redis命令
        /// </summary>
        /// <param name="command">redis命令</param>
        /// <param name="message">message</param>
        /// <param name="commandFlags">commandFlags</param>
        /// <returns></returns>
        protected override async Task<EMRedisResult> ExecuteAsync(RedisCommand command, RedisMessage message, CommandFlags flags = CommandFlags.None)
        {
            var tempTuple = GetSmartClientServer(message.Key);
            var clientServer = tempTuple.Item1;
            var switched = tempTuple.Item2;
            return await clientServer.ExecuteCommandAsync(command, message, flags).ContinueWith((t) =>
            {
                var syncRedisClient = switched ? this : _backupClient;
                TrySyncMessage(syncRedisClient, command, message);
                return t.Result;
            }).ConfigureAwait(false);
        }

        public override RedisClientInfo RedisCacheInfo()
        {
            RedisClientInfo info = base.RedisCacheInfo();
            info.ConnectionCache.Nodes.ForEach(x => { x.Available = !CheckHealth(GetClientServerByNodeName(x.Name)); });
            //兼容old pattern
            info.DelayedRecoverySeconds = _configuration.Pool.DelayedRecoverySeconds ?? -1;
            info.RedisFailoverWaitSeconds = _configuration.Pool.RedisFailoverWaitSeconds ?? 10;
            info.SupportSwitch = _configuration.AllowSwitch;
            info.SupportSync = _configuration.AllowSync;
            info.ConnectionCache.BackupConnectionCache = BuildBackupConnectCache(_configuration.BackupOptions, CurrentBackup);
            return info;
        }
        /// <summary>
        /// 获取可用的ClientServer
        /// </summary>
        /// <param name="key">redis key</param>
        /// <returns></returns>
        public override Tuple<RedisClientServer, bool> GetSmartClientServer(string key)
        {
            var clientServer = base.GetClientServer(key);
            bool switched = false;      //是否发生切换
            if (_configuration.AllowSwitch && _backupClient != null && !CheckHealth(clientServer))
            {
                switched = true;
                clientServer = _backupClient.GetSmartClientServer(key).Item1;
            }
            return new Tuple<RedisClientServer, bool>(clientServer, switched);
        }
        public override IRedisClient BackupClient
        {
            get
            {
                if (_backupClient == null)
                {
                    if (ActiveBackupClient())
                        OnClientStateChangedEvent?.Invoke(this, new ClientStateChangeEventArgs { ClientName = _configuration.ClientName, Type = 3 });
                }
                return _backupClient;
            }
        }
        internal RedisClient CurrentBackup { get { return _backupClient; } }

        #region private

        private RedisConnectCache BuildBackupConnectCache(RedisClientConfiguration backupOptions, RedisClient backupClient)
        {
            if (backupOptions == null) return null;
            RedisConnectCache entity = new RedisConnectCache();
            entity.Name = backupOptions.ClientName;
            entity.HasActive = backupClient != null;
            entity.Nodes = backupOptions.Pool.Nodes.Select((n) =>
              {
                  return new RedisConnectNode
                  {
                      Available = backupClient == null ? false : !backupClient.CheckHealth(backupClient.GetClientServerByNodeName(n.Name)),
                      DB = n.DB,
                      Name = n.Name,
                      SlotFrom = n.SlotFrom,
                      SlotTo = n.SlotTo,
                      Master = n.Master,
                      Slaves = n.Slaves
                  };
              }).ToList();
            entity.BackupConnectionCache = null;
            return entity;
        }

        private static object _obj = new object();
        /// <summary>
        /// 激活备 RedisClientServer
        /// </summary>
        /// <returns></returns>
        private bool ActiveBackupClient()
        {
            bool flag = false;
            if (_backupClient == null)
            {
                lock (_obj)
                {
                    if (_backupClient == null)
                    {
                        if (_configuration.BackupOptions != null)
                        {
                            _backupClient = new RedisClient(_configuration.BackupOptions, _clientServerPoolFactory);
                            RedisLogManager.Info($"redisclient {_configuration.ClientName} backupRedisClient {_configuration.BackupOptions.Pool.Name} actived success.");
                            flag = true;
                        }
                    }
                }
            }
            return flag;
        }

        /// <summary>
        /// 尝试消息同步
        /// option allowsync & RedisCommand belong to master
        /// </summary>
        /// <param name="toRedisClient">同步的RedisClient</param>
        /// <param name="command">redis command</param>
        /// <param name="message">message</param>
        private void TrySyncMessage(RedisClient toRedisClient, RedisCommand command, RedisMessage message)
        {
            if (toRedisClient == null) return;
            if (!_configuration.AllowSync) return;
            if (message.IsSlaveCommand(command)) return;
            try
            {
                var clientServer = toRedisClient.GetClientServer(message.Key);
                clientServer.ExecuteCommand(command, message, CommandFlags.FireAndForget);
            }
            catch { }
        }

        #endregion

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_backupClient != null)
                    _backupClient.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}
